using System;
using System.Collections;
using SimpleNode = Velocity4Net.Runtime.Parse.AST.SimpleNode;
using NodeUtils = Velocity4Net.Runtime.Parse.AST.NodeUtils;
using Token = Velocity4Net.Runtime.Parse.Token;
using InternalContextAdapter = Velocity4Net.Ctx.InternalContextAdapter;
using Node = Velocity4Net.Runtime.Parse.AST.INode;
using System.IO;
using Velocity4Net.Runtime.Parse.AST;
using System.Text;
using Velocity4Net.Runtime.Parse;

namespace Velocity4Net.Runtime.Directives
{


    /**
     *  Macro implements the macro definition directive of VTL.
     *
     *  example :
     *
     *  #macro( isnull $i )
     *     #if( $i )
     *         $i
     *      #end
     *  #end
     *
     *  This object is used at parse time to mainly process and register the
     *  macro.  It is used inline in the parser when processing a directive.
     *
     * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
     * @author <a href="hps@intermeta.de">Henning P. Schmiedehausen</a>
     * @version $Id: Macro.java 746438 2009-02-21 05:41:24Z nbubna $
     */
    public class Macro : Directive
    {
        private static bool debugMode = false;

        /**
         * Return name of this directive.
         * @return The name of this directive.
         */
        public override String Name
        {
            get
            {
                return "macro";
            }
            set { }
        }

        /**
         * Return type of this directive.
         * @return The type of this directive.
         */
        public override int Type
        {
            get
            {
                return 1;
            }
        }

        /**
         * Since this class does no processing of content,
         * there is never a need for an internal scope.
         */
        public override bool ScopeProvided
        {
            get
            {
                return false;
            }
        }

        /**
         *   render() doesn't do anything in the final output rendering.
         *   There is no output from a #macro() directive.
         * @param context
         * @param writer
         * @param node
         * @return True if the directive rendered successfully.
         * @throws IOException
         */
        public override bool Render(InternalContextAdapter context,
                               TextWriter writer, INode node)
        {
            /*
             *  do nothing : We never render.  The VelocimacroProxy object does that
             */

            return true;
        }

        /**
         * @see org.apache.velocity.runtime.Directive#init(org.apache.velocity.runtime.RuntimeServices, org.apache.velocity.context.InternalContextAdapter, org.apache.velocity.runtime.parser.node.Node)
         */
        public override void Init(RuntimeServices rs, InternalContextAdapter context,
                         Node node)
        {
            base.Init(rs, context, node);


            // Add this macro to the VelocimacroManager now that it has been initialized.        
            String[] argArray = getArgArray(node, rs);
            int numArgs = node.jjtGetNumChildren();
            rs.AddVelocimacro(argArray[0], node.jjtGetChild(numArgs - 1), argArray, node.TemplateName);
        }

        /**
         *  Used by Parser.java to do further parameter checking for macro arguments.
         */
        public static void checkArgs(RuntimeServices rs, Token t, Node node,
                                              String sourceTemplate)
        {
            /*
             *  There must be at least one arg to  #macro,
             *  the name of the VM.  Note that 0 following
             *  args is ok for naming blocks of HTML
             */
            int numArgs = node.jjtGetNumChildren();

            /*
             *  this number is the # of args + 1.  The + 1
             *  is for the block tree
             */
            if (numArgs < 2)
            {

                /*
                 *  error - they didn't name the macro or
                 *  define a block
                 */
                rs.getLog().error("#macro error : Velocimacro must have name as 1st " +
                                  "argument to #macro(). #args = " + numArgs);

                throw new MacroParseException("First argument to #macro() must be " +
                        " macro name", sourceTemplate, t);
            }

            /*
             *  lets make sure that the first arg is an ASTWord
             */
            int firstType = node.jjtGetChild(0).NodeType;
            if (firstType != ParserTreeConstants.JJTWORD)
            {
                throw new MacroParseException("First argument to #macro() must be a"
                        + " token without surrounding \' or \", which specifies"
                        + " the macro name.  Currently it is a "
                        + ParserTreeConstants.jjtNodeName[firstType], sourceTemplate, t);
            }
        }

        /**
         * Creates an array containing the literal text from the macro
         * arguement(s) (including the macro's name as the first arg).
         *
         * @param node The parse node from which to grok the argument
         * list.  It's expected to include the block node tree (for the
         * macro body).
         * @param rsvc For debugging purposes only.
         * @return array of arguments
         */
        private static String[] getArgArray(Node node, RuntimeServices rsvc)
        {
            /*
             * Get the number of arguments for the macro, excluding the
             * last child node which is the block tree containing the
             * macro body.
             */
            int numArgs = node.jjtGetNumChildren();
            numArgs--;  // avoid the block tree...

            String[] argArray = new String[numArgs];

            int i = 0;

            /*
             *  eat the args
             */

            while (i < numArgs)
            {
                argArray[i] = node.jjtGetChild(i).FirstToken.image;

                /*
                 *  trim off the leading $ for the args after the macro name.
                 *  saves everyone else from having to do it
                 */

                if (i > 0)
                {
                    if (argArray[i].StartsWith("$"))
                    {
                        argArray[i] = argArray[i]
                            .Substring(1, argArray[i].Length - 1);
                    }
                }

                argArray[i] = string.Intern(argArray[i]);
                i++;
            }

            if (debugMode)
            {
                StringBuilder msg = new StringBuilder("Macro.getArgArray() : nbrArgs=");
                msg.Append(numArgs).Append(" : ");
                macroToString(msg, argArray);
                rsvc.getLog().debug(msg);
            }

            return argArray;
        }

        /**
         * For debugging purposes.  Formats the arguments from
         * <code>argArray</code> and appends them to <code>buf</code>.
         *
         * @param buf A StringBuffer. If null, a new StringBuffer is allocated.
         * @param argArray The Macro arguments to format
         *
         * @return A StringBuffer containing the formatted arguments. If a StringBuffer
         *         has passed in as buf, this method returns it.
         * @since 1.5
         */
        public static StringBuilder macroToString(StringBuilder buf,
                                                        String[] argArray)
        {
            StringBuilder ret = (buf == null) ? new StringBuilder() : buf;

            ret.Append('#').Append(argArray[0]).Append("( ");
            for (int i = 1; i < argArray.Length; i++)
            {
                ret.Append(' ').Append(argArray[i]);
            }
            ret.Append(" )");
            return ret;
        }
    }
}
